[SwiftUI] iOS 17以降の@Environmentまとめ

[SwiftUI] iOS 17以降の@Environmentまとめ

Clock Icon2024.11.01

こんにちは。きんくまです。

iOS 17以降の@Environment

@Environmentの役割は以下の3つあります

  1. ObservationフレームワークのModelを読み書き(キーは指定しない)(iOS17以降)
  2. フレームワーク側で用意された値を、キーを指定して読み取り
  3. 自作のキーと値を用意して読みとり + (親から値のセット)(Xcode16で@Entryが追加)

1. ObservationフレームワークのModelを読み書き(キーは指定しない)(iOS17以降)

最初は前回ブログに書いた使い方です

[SwiftUI] iOS16以降の@EnvironmentObjectとiOS17以降の@Environment

この場合はキーを指定せずに使います。
データの読み書きに方向性はなく、下の階層のViewで変更されたものが、上の階層にも伝わります。

2. フレームワーク側で用意された値を、キーを指定して読み取り

次にフレームワーク側で用意された各種の値を読み取ったり、場合によってはfuncになっていたりするので、それを使ったりする使い方です。

struct ContentView: View {
    @Environment(\.colorScheme) private var colorScheme
    @Environment(\.openURL) private var openURL

    var body: some View {
        VStack {
            Text("ColorScheme \(colorScheme)")
            Button {
                guard let url = URL(string: "https://www.google.com") else {
                    return
                }
                openURL(url)
            } label: {
                Text("openURL")
            }
        }
    }
}

上記では、以下をしています

  • colorSchemeで端末のカラー設定(light/dark)を読み取ります
  • openURLで、URLを開く処理を行います。こちらはfuncなので、oepnURL()で実行します。上記の場合、実行するとSafariが開きます

フレームワークで定義されたものはget onlyで、直接は変更できません。
ですが、OpenURLActionなどのように、挙動を変更できるものもあります

Text("Visit [Example Company](https://www.example.com) for details.")
    .environment(\.openURL, OpenURLAction { url in
        handleURL(url) // Define this method to take appropriate action.
        return .handled
    })

OpenURLAction

参考

たくさんのEnvironmentが載っています
Every SwiftUI Environment Value explained

3. 自作のキーと値を用意して読みとり + (親から値のセット)(Xcode16で@Entryが追加)

2はフレームワークから提供されていますが、そうではなく自作のキーと値を読み取ることも可能です。

struct MainBackgroundColorEnvionmentKey: EnvironmentKey {
    static var defaultValue: Color = .blue
}

extension EnvironmentValues {
    public var mainBackgroundColor: Color {
        get { self[MainBackgroundColorEnvionmentKey.self] }
        set { self[MainBackgroundColorEnvionmentKey.self] = newValue }
    }
}

上記のようにキーとデフォルト値を定義すれば、以下のようにViewから使うことが可能です。

struct ContentView: View {
    @Environment(\.mainBackgroundColor) var mainBackgroundColor: Color

    var body: some View {
        Color(mainBackgroundColor)
    }
}

もし、子供のViewに渡すときにデフォルト値を変更したい場合は以下のようにします

struct SubView: View {
    @Environment(\.mainBackgroundColor) var mainBackgroundColor: Color
    var body: some View {
        Color(mainBackgroundColor)
    }
}

struct ContentView: View {
    var body: some View {
        VStack {
            SubView()
        }.environment(\.mainBackgroundColor, .red) //ここです
    }
}

Xcode16で追加された@Entry

自作の定義があるときに

struct MainBackgroundColorEnvionmentKey: EnvironmentKey {
    static var defaultValue: Color = .blue
}

extension EnvironmentValues {
    public var mainBackgroundColor: Color {
        get { self[MainBackgroundColorEnvionmentKey.self] }
        set { self[MainBackgroundColorEnvionmentKey.self] = newValue }
    }
}

これをXcode16では@Entryで書けます。便利
あと、Xcode16から書けますが、使えるのはiOS13からOKです。

extension EnvironmentValues {
    @Entry var mainBackgroundColor: Color = .blue
}

データの方向性

自作のキーの場合は、自分より近い親での値が適用されます。(実験してたらそうなった)

extension EnvironmentValues {
    @Entry var mainBackgroundColor: Color = .blue
}

struct GrandchildView: View {
    @Environment(\.mainBackgroundColor) var mainBackgroundColor: Color

    var body: some View {
        VStack {
            Text("GrandchildView")
            Color(mainBackgroundColor)
                .frame(width: 50, height:50)
        }
        .padding(20)
        .border(Color.gray)
        .environment(\.mainBackgroundColor, .red)
    }
}

struct ChildView: View {
    @Environment(\.mainBackgroundColor) var mainBackgroundColor: Color

    var body: some View {
        VStack {
            Text("ChildView")
            Color(mainBackgroundColor)
                .frame(width: 50, height:50)
            GrandchildView()
        }
        .padding(20)
        .border(Color.gray)
        .environment(\.mainBackgroundColor, .purple)
    }
}

struct ContentView: View {
    @Environment(\.mainBackgroundColor) var mainBackgroundColor: Color

    var body: some View {
        VStack {
            Text("ContentView")
            Color(mainBackgroundColor)
                .frame(width: 50, height:50)
            ChildView()
        }
        .padding(20)
        .border(Color.gray)
        .environment(\.mainBackgroundColor, .green)
    }
}

上記の結果はこうなります。

241101-Environment

挙動としては、

  • 自分自身でセットした.environmentは、自分の@Environmentには無視される
  • 自分自身でセットした.environmentは、自分より子供のViewの@Environmentに適用される
  • 自分より親のViewでセットされた.environmentは、より自分に近い階層の値が優先されて適用される

参考

Observation Framework in iOS 17
How to create and use custom environment values
@Entry macro: Creating custom environment values in SwiftUI
Introducing Entry macro in SwiftUI
Adding values to the SwiftUI environment with Xcode 16’s Entry macro

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.